Skip to content

πŸ€– feat: RLM Mode β€” kernel-first exclusive PTC posture with persistent kernel, context isolation, and continual-harness features - #3900

Open
ThomasK33 wants to merge 204 commits into
mainfrom
research-qr9r
Open

πŸ€– feat: RLM Mode β€” kernel-first exclusive PTC posture with persistent kernel, context isolation, and continual-harness features#3900
ThomasK33 wants to merge 204 commits into
mainfrom
research-qr9r

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Adds RLM Mode β€” an opt-in, kernel-first execution posture for PTC inspired by PrimeIntellect's prime-agent architecture β€” plus the continual-harness features around it (refinement journal with rollback, /refine trajectory distillation, family messaging, branch summarization, compaction improvements) and a measurement harness (shux rlm-eval) that every major design decision in this PR was validated against.

With the RLM experiment off, behavior is byte-identical to main (pinned by composition tests and replay-verify on live sessions). With it on, code_execution becomes the primary tool backed by a persistent per-workspace QuickJS kernel.

Background

Research into prime-agent (which posted strong vendor-reported eval results) identified two core ideas worth porting: a single persistent code kernel where in-kernel data never transits model context, and a self-modifying harness with journaled, reversible edits. Mux's Track 1 foundation (journal kit, durable events, sandbox host, replay harness β€” #3865/#3872) provided the substrate; this PR is "Track 2" built on it, implemented via the phased conductor workflow in workflows/track2-rlm-implementation.js (per-phase quality gates, adversarial review, live dogfooding).

Implementation

RLM kernel (phases r1, r4, r5, r12):

  • rlm-mode experiment, nested under PTC; exclusive-only β€” enabling it forces the kernel-first narrowed toolset (supplement-mode RLM measured ~2x flat cost and was removed)
  • Persistent per-workspace mount: guest vars survives calls/turns/restarts via journaled snapshots
  • Kernel context isolation: nested shux.* results never enter model context (compact {tool, ok, bytes} summaries); the model's channels are its return value (offloaded via handles >16KB), capped console output, and vars
  • shux.load({path, key}): host-side bulk file ingestion straight into vars (record shows {key, bytes, lines, preview} only)
  • shux.task_spawn + shux.events(): fire-and-forget sub-agents with admission handles, asyncify-safe event drain
  • Batching guidance baked into the kernel-first preamble ("write complete programs")

Continual harness (r2, r6, r11):

  • Every memory/skill mutation journals an invertible refinement durable event (blob-backed inverses)
  • Rollback engine with rollbackOf lineage: shux run debug refinements CLI + RLM-gated refinement_rollback tool
  • /refine: bounded trajectory-distillation pass (dream-agent machinery) applying smallest evidence-backed edits, journaled and reversible

Agent ops (r3, r7, r8, r9):

  • Nuclear-family messaging: task_message_parent / task_message_sibling (RLM stamped on task records at spawn; strict same-parent scoping; server-side labels)
  • RLM-gated compaction keep-recent floor + cumulative read-file tracking
  • Branch summarization on fork/edit-resend (background generation, tail-guarded append)
  • scripts/gate_fingerprint.sh verification-loop memoizer

Measurement (scripts/rlm-eval/, make rlm-eval): scenario x config x seed A/B runner extracting mechanical metrics (tokens, cost, wall time, peak context, vars adoption, batch factor, compactions) from session artifacts.

Validation

  • Key measured results (sonnet-5 / opus-5 / gpt-5.6-sol; fable-5 at medium):
    • Context isolation: 504KB file load -> 867 bytes model-visible (0.17%); pre-fix the same task leaked 610KB into context and cost 10x flat tools
    • RLM-exclusive vs flat tools: -30 to -63% cost in 7/8 model x scenario pairs, faster in 6/8, all cells correct; organic vars adoption 15/16
    • Batching preamble (cross-build A/B): sonnet organic batch factor 2.7 -> 3.5 (3/4 seeds fold all 6 loads into one eval, -42% tokens)
  • Every phase passed an independent gate run + adversarial review + live dev-server-sandbox dogfood with replay-verify PASS (evidence in the workflow run reports)
  • Post-rebase onto the Shux rename: full static-check green; kernel suites (code_execution 50, toolBridge/typeGenerator 43, toolAssembly 14, sandboxHost 25) green; kernel surfaces adopt shux-primary naming with the mux.* alias intact

Risks

  • RLM-off regression risk is the headline concern and is heavily defended: composition tests pin byte-identity per flag combination, and replay-verify was run on live RLM-off control sessions at each phase. Highest-traffic shared code touched: toolAssembly, code_execution, compaction paths (RLM-gated), task spawn paths (flag stamping).
  • RLM-on surfaces are experimental by declaration; known rough edges: peak per-request context is higher when shux.load materializes large files (latent pressure on multi-MB corpora), and one sonnet seed still fragments batching.
  • /refine auto-applies edits (no approval UI in v1) β€” mitigated by journal + rollback + immutable-base guard rails.

Pains

  • The mid-series mux -> shux rename on main required conflict resolution across the kernel commits (namespace, type-generator identifiers, description text).
  • Sub-agent dogfooding infrastructure failures (background-monitor wakes, uncommitted-work timeouts, transient gateway model errors) shaped several workflow-hardening commits.

Generated with mux β€’ Model: anthropic:claude-fable-5 β€’ Thinking: xhigh β€’ Cost: $763.80

@mintlify

mintlify Bot commented Aug 20, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Mux 🟒 Ready View Preview Aug 20, 2026, 5:42 PM

πŸ’‘ Tip: Enable Workflows to automatically generate PRs for you.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

…inement journal, RLM mode experiment)

Conductor for implementing prime-agent-inspired RLM/continual-harness features behind an opt-in RLM sub-experiment of PTC. Mirrors workflows/track1-implementation.js: per-phase implement -> gate+adversarial-review -> fix rounds -> dogfood.
… code_execution

RLM Mode is an opt-in sub-experiment of Programmatic Tool Calling (flat
flag, gated on the PTC parent at call sites, nested under the PTC toggle
in Settings, mirroring the Memory Hot Set precedent). When enabled with
PTC and sandbox context, code_execution runs on the persistent
per-workspace kernel mount: the guest vars namespace survives across
calls/turns and restarts via snapshots, and the tool description
advertises those kernel semantics. MUX_SANDBOX_PERSISTENT_MOUNTS=1
remains a dev/test override with unchanged behavior. With the experiment
off (and env unset) behavior is byte-identical to before: fresh runtime
per call and today's description.

The rlm flag plumbs through the experiments path end to end:
ExperimentsSchema (send options) -> aiService.streamMessage ->
applyToolPolicyAndExperiments.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Every mutating memory command (create/str_replace/insert/delete/rename) and
every agent_skill_write/agent_skill_delete now appends exactly one
'refinement' durable event to the acting workspace's session journal
(sharedDurableEventJournal), carrying an inverse payload that byte-exactly
restores the prior file state. Prior contents over 4KB are offloaded to the
session blob store (BlobRef), mirroring hook-context. Evidence records
{workspaceId, toolName, toolCallId?, actor?}.

Always-on and purely additive: journaling failures never fail the tool
(log.debug + continue), read-only ops and failed mutations write no rows.
Cross-workspace caveat (v1): memory/skill files are global/project-scoped
while the journal is per-session; rows land in the acting workspace's log.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Standalone, always-on-by-usage gate memoizer: 'fingerprint' hashes HEAD sha +
'git diff HEAD' + sorted untracked-not-ignored files with content hashes;
'record <gate> <pass|fail>' and 'check <gate>' store/look up results in a JSON
file inside the worktree-local git dir (git rev-parse --git-path), so records
are never committed and never invalidate themselves.

wait_pr_ready.sh integration was skipped intentionally: it has no local
validation step (it only orchestrates remote Codex/review/CI gates), per the
phase brief's conditional.

Tests spawn the real script against hermetic temp git repos and cover
stability, pass/fail round-trip, tracked-edit / untracked-file / staged-change
invalidation, and corrupt-store self-healing.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…-handle events

Under an RLM persistent mount, nested mux.* results and code_execution
return values whose JSON serialization exceeds 16KB stop entering the
model context: the model-visible record becomes {handle, preview, size}
(plus a follow-up hint for return values) while the full value stays in
the guest at vars.__hN (monotonic per scope via vars.__handleSeq, so it
snapshots/restores with vars), in the content-addressed blob store, and
in one result-handle durable event whose preview mirrors the
model-visible string exactly. Handle bytes retained in vars are capped
with oldest-first eviction (never the newest handle); the blob remains
the durable copy. RLM off / ephemeral runtimes are byte-identical to
today.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…escription)

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…ervice

finalizeAgentTaskReport now invokes sandboxHostService.postTaskTerminalEvent
(fire-and-forget, gated on no foreground waiters) so spawned-task completions
reach the guest host-event queue in production β€” previously the hook had zero
production callsites and mux.events() always drained empty. Regression tests
cover both the posted-event and waiter-suppression branches.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…h lineage

listRefinements/rollbackRefinement make the r2 journal actionable: rollbacks
apply the recorded inverse (inline or blob-backed) through atomic writes,
journal their own refinement row with rollbackOf (so double inversion works),
refuse already-rolled-back targets, refuse divergence (later overlapping rows,
deleted/recreated files, content drift for rollback rows) unless forced, and
confine every touched path to memory scope roots / skill directories with
lexical + symlink escape checks that force can never override.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…orce

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Assembled in toolAssembly from the sandbox context inside the PTC branch, so
the tool only exists when RLM mode is on (nested under the PTC parent); with
the experiment off the toolset β€” and thus every provider request β€” stays
byte-identical. Force stays CLI-only: divergence overrides are a human call.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…refinement_rollback

Signed-off-by: Thomas Kosiewski <tk@coder.com>
… memory to current session

- P1: the later-rows divergence check now nets out rollback lineage: rows
  whose effect was itself rolled back are skipped, and live rollback chains
  conflict only when their parity re-applies an edit or rewinds past the
  target β€” so LIFO multi-edit unrolling works for model tool calls without
  force, while re-applied edits (rollback-of-rollback) still refuse.
- P2: workspace-scope memory confinement resolves strictly to the current
  session's memory root (<sessionDir>/memory) instead of any session subdir
  under sessionsDir, closing the cross-workspace write leak.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…tCode on undefined assignment)

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…action)

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…oundary copies, read-file tracking

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…rendering

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…est metadata, optional chain

Signed-off-by: Thomas Kosiewski <tk@coder.com>
…tch + staleness guard)

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59b6d5fb98

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/refinement/refineRunner.ts Outdated
Comment thread src/node/services/agentSession.ts Outdated
Comment thread src/node/services/sandbox/sandboxHostService.ts Outdated
Comment thread src/node/services/agentSession.ts
Comment thread src/node/services/refinement/refineService.ts
Comment thread src/node/services/refinement/refineService.ts Outdated
…route (Codex r31)

resolveDreamModelString's fallback tier read only legacy aiSettings,
which updateAgentAISettings never rewrites β€” so a workspace whose
current model is a per-agent private/gateway route could fall through a
stale legacy model or the built-in direct-Anthropic default, shipping up
to 160K chars of transcript-derived content off the selected route.

Absent an explicit dream override (workspace dream bucket or global
dream default β€” both explicit consent), fallbacks now derive from the
same route-confined candidate list as branch summaries: the selected
agent's model, other per-agent models, then the legacy model as a
compatibility fallback.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5f825c2b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/refinement/refineService.ts Outdated
Comment thread src/node/services/refinement/refineService.ts
Comment thread src/node/services/branchSummary.ts Outdated
Comment thread src/node/services/workspaceService.ts
…dmission

- refineRunner: neutralize embedded workspace_trajectory delimiter
  sequences before interpolation (branch-summary parity) so transcript
  content cannot close the data region and reach instruction level.
- agentSession/historyService: persist family payload rows + trigger user
  row as ONE durable write (appendManyToHistory) β€” separate appends left
  a crash window that stranded an orphaned payload in history.
- sandboxHostService: rebuild vars.__loadMeta as a fresh plain object
  each retention pass; a frozen/write-swallowing registry silently
  exempted new loads from the 4MiB managed cap.
- agentSession: reserve edit turn admission (editAdmissionDepth in
  isBusy) across truncate + branch summary + row appends; a concurrent
  send could observe idle mid-edit and interleave rows.
- refineService: hold a cross-process lockfile across apply
  (XUM_ALLOW_MULTIPLE_INSTANCES backends could double-apply a staged
  set), and gate /refine on the renderer's effective experiment flags
  riding the request (backend override persistence is async/best-effort).
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1ec62404b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/refinement/refineService.ts Outdated
… durability, usage-write drain, full-clear sandbox discard)

- refineService: report failed staged edits on the record + audit row instead
  of classifying an all-failed apply as a successful no-op; never-executed
  skips (tool unavailable / schema-rejected) stay out of the attempted set and
  retain the staged set for retry; audit/proposal row appends propagate
  failure so the staged set is consumed only after the row durably lands
- branchSummary: track usage writes abandoned by the deadline race and drain
  them in clearPendingBranchSummary so removal's usage rollup cannot miss a
  late write that would recreate the deleted session directory
- workspaceService: full history clear and destructive non-compaction replace
  durably discard sandbox kernel state (same posture as resetContext)
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed all 4 round-33 findings in a5f825c..9066a7a:

  • P2 failed staged edits reported (refineService.ts:572): the apply loop now records per-edit failures (failed: [{description, reason}] on RefineRecord); an all-failed apply is no longer classified as a successful no-op β€” failures reach the durable audit row (- FAILED: …) and the /refine toast. Never-executed skips (tool unavailable, schema-rejected input) have no side effects, so they stay out of the attempted set and the staged set is retained for a later retry; executed edits remain attempted-and-never-replayed.
  • P2 audit-row durability (refineService.ts:603): appendSummaryMessage now returns whether the row durably landed. A failed applied-mode audit append fails the apply and retains the staged set (the persisted baseline + attempted IDs reproduce the audit row with zero re-mutation on retry); the staged set is cleared only after the audit row actually lands. Same propagation for the staged-mode proposal row, whose loss previously dead-ended approval.
  • P2 branch-summary usage-write drain (branchSummary.ts:482): usage writes abandoned by the deadline race are now tracked in a per-workspace registry; clearPendingBranchSummary drains them (looped, never-rejecting) after aborting/awaiting the writer, so removal's usage rollup cannot miss a late write and SessionUsageService.writeFile() can no longer recreate the deleted session directory. The caller-facing deadline race is unchanged.
  • P2 sandbox discard on full clear (workspaceService.ts:9959): a full history clear now applies the same durable sandboxHostService.discardScope invalidation as resetContext (partial-failure Err posture); also added to the destructive non-compaction replaceHistory boundary. Partial truncation and compaction replaces intentionally keep kernel state.

Validation: make static-check green; refineService (31), branchSummary (46), workspaceService (317), chatCommands (81) suites pass locally.

@codex review

… unjournaled successes (Codex r33 follow-up)

An unjournaled success (memory write succeeded, refinement-journal append
failed) left no durable trace once the crash-resumed apply skipped the
attempted edit with its in-pass counter at zero: recovery misreported the
real, rollback-less mutation as a no-op and consumed the staged set. The
staged file now persists succeededToolCallIds alongside the attempted set,
and untrackedApplied derives from persisted successes minus journaled rows.
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed the fifth round-33 finding in 9066a7a..c24bfe7:

  • P2 unjournaled successes across recovery (refineService.ts:607): the staged file now persists succeededToolCallIds alongside the attempted set (rewritten in the same per-edit progress persist), and untrackedApplied is derived as a set difference β€” persisted successes minus journaled rows β€” instead of an in-pass counter minus applied.length. A crash-resumed apply (attempted edit skipped, counter at zero) therefore reconstructs the prior unjournaled success and reports it as untracked in the audit row rather than misclassifying the real, rollback-less mutation as a no-op and consuming the staged set. New test covers the exact crash shape: success + failed journal append + crash before the audit row, then recovery reporting untrackedApplied: 1.

Also minimized the stale "Something went wrong" error comment and the security-review note that were blocking the Codex Comments check.

Validation: make static-check green; refinement suites 76/76 pass (3 new tests this round).

@codex review

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c24bfe7212

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/refinement/refineService.ts
Comment thread src/node/services/refinement/refineService.ts
Comment thread src/browser/utils/messages/modelMessageTransform.ts Outdated
…staging/apply cross-process lock, Anthropic-only assistant merge)

- refineService/refineStaging: persist failedToolCalls (id + reason) in the
  per-edit progress rewrite and rebuild the record's failures from persisted
  outcomes, so a crash-resumed apply reports the approved edit's failure
  instead of misclassifying a no-op and consuming the staged set silently
- refineService: staged-set replacement + proposal publication in runLocked
  now acquire the same cross-process refine-apply.lock as apply, so a /refine
  in one backend cannot be overwritten by a concurrent apply's stale staged
  snapshot spread under XUM_ALLOW_MULTIPLE_INSTANCES=1
- modelMessageTransform: the consecutive-assistant merge pass is gated to
  Anthropic (the only provider rejecting adjacent assistant rows) and now
  preserves original text parts verbatim so part-level providerOptions
  survive instead of being re-joined into one plain string
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed all 3 round-34 findings in c24bfe7..b141504:

  • P2 persist failed outcomes across recovery (refineService.ts:590): the per-edit progress rewrite now persists failedToolCalls ({toolCallId, reason}) alongside attempted/succeeded IDs, and the record's failed list is rebuilt after journal correlation from this pass's never-executed skips plus the persisted executed failures (excluding journaled/succeeded IDs). A crash-resumed apply therefore reports the approved edit's failure in the audit row instead of misclassifying a no-op and silently consuming the staged set. New test covers the exact shape: executed failure + crash before the audit row, resume reporting failed: 1 with a durable FAILED: audit line.
  • P2 serialize staging with cross-process apply (refineService.ts:829): runLocked's staged-set replacement (save and the no-edits clear) plus proposal-row publication now acquire the same refine-apply.lock file lock apply holds, so a /refine in one backend can no longer be overwritten by a concurrent apply's stale staged-snapshot spread under XUM_ALLOW_MULTIPLE_INSTANCES=1. A held lock yields a descriptive Err with nothing replaced and no proposal row published (new test).
  • P2 gate assistant-row merging (modelMessageTransform.ts:1233): the consecutive-assistant merge pass is now Anthropic-only β€” other providers accept adjacent assistant rows and their request bytes are unchanged (pass-through asserted for openai/google). The merge also preserves the original text parts verbatim as separate content blocks instead of re-joining into one string, so part-level providerOptions (e.g. cacheControl) survive; empty text parts are filtered since Anthropic rejects empty blocks.

Validation: make static-check green; refinement + messages suites 510/510 pass locally (3 new tests).

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b141504f74

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/utils/messages/modelMessageTransform.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: b141504f74

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread src/node/services/workspaceService.ts
…nt merge (Codex r35)

History recorded with extended thinking can carry a signed-reasoning
assistant row whose trailing text part is empty; merging a synthetic summary
into it copied that empty block into the request, which Anthropic rejects.
Both sides of the merge now drop empty text parts while preserving non-text
parts (signed reasoning) and part-level providerOptions verbatim.
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed the round-35 finding in b141504..48392e0:

  • P2 filter empty blocks from both sides of the merge (modelMessageTransform.ts:1053): the consecutive-assistant merge now drops empty text parts from the previous row as well as the incoming one (shared dropEmptyText predicate), so a signed-reasoning row whose trailing text part is empty no longer carries that empty block into the merged Anthropic request. Non-text parts (signed reasoning) and part-level providerOptions pass through verbatim. New test covers the exact shape: [reasoning(signed), text ""] + synthetic summary β†’ merged [reasoning(signed), summary text] with no empty block. (With thinking ON the summary row gains a placeholder reasoning part and is no longer text-only, so the merge doesn't fire there β€” the affected path is signed-reasoning history replayed with thinking off.)

Validation: make static-check green; messages suites 433/433 pass locally.

@codex review

…th (Codex r35 security follow-up)

A reset that failed AFTER writing its boundary but BEFORE its durable
cleanup landed left the retry on the no-op branch (no provider-eligible
rows after the boundary), reporting success while a restart could still
restore pre-reset post-compaction carryover or kernel vars across the
boundary. The no-op branch now re-runs both idempotent cleanup steps
(pending-state unlink, sandbox discard) durable-or-Err before reporting
noop.
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed the round-35 security follow-up in 48392e0..6db1250:

  • P2 retry cleanup after a partial context reset (workspaceService.ts:10074): the resetContext no-op branch (no provider-eligible rows after an already-written reset boundary) now re-attempts both durable cleanup steps β€” pending post-compaction unlink and sandbox discardScope β€” before reporting noop, each with the same durable-or-Err partial-failure posture as the main path. A retry after a partial reset can therefore no longer report success while a restart could restore pre-reset carryover or kernel vars across the boundary. Both steps are idempotent (ENOENT unlink succeeds; tombstone re-publish is harmless), so a genuinely clean no-op stays a no-op. The test that pinned the second-call noop now asserts the retry fails while the invalidation still fails and settles as noop only once the re-run cleanup lands.

Note: the Test / Unit failure on 48392e0 (WorkflowRunner > replays completed agent steps without respawning child tasks) is unrelated to this PR's diff and passes locally 3/3; the fresh CI run on this push supersedes it.

Validation: make static-check green; workspaceService suite 317/317 locally.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. πŸ‘

Reviewed commit: 6db12509e6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 6db12509e6

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread src/node/services/refinement/refineService.ts Outdated
… (Codex r37 security)

getLastMessages crosses reset boundaries and pages into the sealed archive,
so after /clear --soft a pre-reset prompt injection could steer the staged
proposal β€” durably appended AFTER the boundary and re-entering model-visible
context, persisting to memory/skills on approval. The distillation read now
uses getHistoryFromLatestBoundary + the provider context-boundary slice
(tail-capped as before), timeline events get the same cutoff, and the
approval-hash scan never crosses a reset backwards (pre-reset proposals fail
closed; compaction remains crossable so pre-compaction proposals stay
approvable).
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed the round-37 security finding in 6db1250..b08f29d:

  • P2 limit refinement input to the active context segment (refineService.ts:742): the distillation read now uses getHistoryFromLatestBoundary() plus sliceMessagesForProviderFromLatestContextBoundary() (tail-capped at REFINE_MAX_MESSAGES as before), so pre-reset rows β€” and archived pre-compaction epochs β€” can no longer enter the refine prompt after /clear --soft; compaction context stays represented via the summary row + preserved tail copies inside the active segment. Timeline events get the same cutoff (filtered by the boundary row's timestamp). Additionally, the approval-hash scan in findNewestStagedProposalHash never crosses a reset boundary backwards: a proposal staged from discarded pre-reset context fails closed at apply ("no staged refine proposal found") and must be restaged from the active segment, while compaction remains crossable so a proposal staged just before an auto-compaction stays approvable.

Tests: prompt-confinement test (pre-reset injected row + pre-boundary timeline event excluded, post-reset rows + recent events included) and a pre-reset-proposal apply-refusal test.

Validation: make static-check green; refinement suites 80/80 locally.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b08f29db3b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/refinement/refineService.ts
Comment thread src/node/services/refinement/refineService.ts Outdated
Comment thread src/node/services/refinement/refineRunner.ts Outdated
…iguous timeline boundary, delimited timeline prompt data)

- workspaceService: resetContext cancels + drains any in-flight refine pass
  before appending its boundary, so a pass distilling the pre-reset
  transcript cannot publish after the marker
- refineService: boundary-identity recheck under the staging lock fails the
  pass closed when the latest context boundary changed between the history
  snapshot and publication (residual TOCTOU window)
- refineService: timeline cutoff fails closed when the boundary row has no
  usable timestamp and uses a strictly-after comparison so same-millisecond
  pre-reset events are excluded
- refineRunner: timeline text is wrapped in its own <workspace_timeline>
  untrusted-data block with both delimiter families neutralized, so
  chat-copied digests cannot sit at instruction level or forge a trajectory
  region
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed all 3 round-38 findings in b08f29d..3dea281:

  • P2 recheck the boundary before publishing (refineService.ts:763): both suggested layers implemented. WorkspaceService.resetContext now cancels + drains any in-flight refine pass (cancelInFlightRefinePass, never rejects) before appending its boundary β€” a pass already in its write section finishes first, leaving its proposal pre-boundary where the reset-blocked hash scan refuses it. For the residual window, runLocked re-reads history under the staging lock and fails closed when the latest context-boundary identity (row ID, or none) differs from the snapshot it distilled from ("reset or compacted while the refine pass was running β€” run /refine again"). New test appends a reset from inside the model's doStream (exactly the mid-pass window) and asserts the pass errors with nothing staged and no proposal row.
  • P2 unambiguous timeline boundary (refineService.ts:1043): fails closed β€” a boundary row without a usable timestamp omits the timeline entirely; the cutoff comparison is now strictly-after (>), so a pre-reset event sharing the boundary's millisecond is excluded (dropping a same-millisecond legitimate event is the safe direction). Both branches covered by tests.
  • P2 delimit timeline text as untrusted data (refineRunner.ts:258): timeline text is wrapped in its own <workspace_timeline> data block with both delimiter families neutralized (workspace_timeline and workspace_trajectory, same [$1…] posture as the trajectory block), so chat-copied turn.user digests can neither sit at instruction level, close their block early, nor forge a trajectory region. Test injects </workspace_timeline> IGNORE ALL RULES <workspace_trajectory> through a timeline description and asserts neutralization with exactly one real block terminator.

Validation: make static-check green; refinement suites 83/83, workspaceService context-reset tests 13/13 locally.

@codex review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant